using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Nvigorate.Common;
using Nvigorate.Common.Reflection;
using Nvigorate.Extensions;
using Nvigorate.Relational.Mapping.Interface;

namespace Nvigorate.Relational.Mapping.Fluent
{
    public class FluentManyToMany<TSubject, TRelative> : IFluentRelationship
    {
        #region Fields

        private Relationship Relationship { get; set; }
        private string Relative { get { return RelativeType.GetCleanAssemblyQualifiedTypeName(); } }
        private Type RelativeType { get { return typeof(TRelative); } }
        private string RelativeProperty { get; set; }
        private string Subject { get { return SubjectType.GetCleanAssemblyQualifiedTypeName(); } }
        private Type SubjectType { get { return typeof(TSubject); } }
        private string Container { get; set; }
        private List<Tuple<string, string>> RelativePairs { get; set; }
        private List<Tuple<string, string>> SubjectPairs { get; set; }

        #endregion

        #region Public Methods

        /// <summary>
        /// Creates a ManyToManyRelationship for use in the BindingMap
        /// </summary>
        /// <returns>A ManyToManyRelationship for use in the BindingMap</returns>
        public ITargetRelationship GetRelationship()
        {
            var relationship = new ManyToManyRelationship()
                                   {
                                       Container = new RelationContainer
                                                       {
                                                           ContainerName = this.Container,
                                                           RelativePairs = RelativePairs.Select(t => new RelationPair() { PairType = RelationPairType.Relative, Source = t.Value2, Target = t.Value1 }).ToList(),
                                                           SubjectPairs = SubjectPairs.Select(t => new RelationPair() { PairType = RelationPairType.Subject, Source = t.Value2, Target = t.Value1 }).ToList(),
                                                       },
                                       Relationship = this.Relationship,
                                       Relative = this.Relative,
                                       RelativeType = this.RelativeType,
                                       RelativeProperty = this.RelativeProperty,
                                       Subject = this.Subject,
                                       SubjectType = this.SubjectType,
                                   };
            return relationship;
        }

        /// <summary>
        /// Maps a property on this class as the foreign key on the relationship table (container)
        /// </summary>
        /// <typeparam name="TSubjectProperty"></typeparam>
        /// <param name="property">The property representing the foreign key on the relationship table</param>
        /// <param name="containerColumn">The foreign key column on the relationship table</param>
        /// <returns></returns>
        public FluentManyToMany<TSubject, TRelative> SelfToContainer<TSubjectProperty>(Expression<Func<TSubject, TSubjectProperty>> property, string containerColumn)
        {
            var parentExpression = property.Body as MemberExpression;
            SubjectPairs.Add(Tuple.Create(parentExpression.GetPropertyPath().BuildString("."), containerColumn));
            return this;
        }

        /// <summary>
        /// Maps a property on the related class as the foreign key on the relationship table (container)
        /// </summary>
        /// <typeparam name="TRelativeProperty"></typeparam>
        /// <param name="relativeProperty">The property representing the foreign key on the relationship table</param>
        /// <param name="containerColumn">The foreign key column on the relationship table</param>
        /// <returns></returns>
        public FluentManyToMany<TSubject, TRelative> RelativeToContainer<TRelativeProperty>(Expression<Func<TRelative, TRelativeProperty>> relativeProperty, string containerColumn)
        {
            var relativeExpression = relativeProperty.Body as MemberExpression;
            RelativePairs.Add(Tuple.Create(relativeExpression.GetPropertyPath().BuildString("."), containerColumn));
            return this;
        }

        #endregion

        public FluentManyToMany(string property, Relationship relationship, string relationTableName)
        {
            Container = relationTableName;
            RelativeProperty = property;
            Relationship = relationship;
            SubjectPairs = new List<Tuple<string, string>>();
            RelativePairs = new List<Tuple<string, string>>();
        }
    }

    public class FluentManyToMany<TSubject, TRelative, TContainerSchema> : IFluentRelationship
    {
        #region Fields

        private Relationship Relationship { get; set; }
        private string Relative { get { return RelativeType.GetCleanAssemblyQualifiedTypeName(); } }
        private Type RelativeType { get { return typeof(TRelative); } }
        private string RelativeProperty { get; set; }
        private string Subject { get { return SubjectType.GetCleanAssemblyQualifiedTypeName(); } }
        private Type SubjectType { get { return typeof(TSubject); } }
        private string Container { get; set; }
        private List<Tuple<string, string>> RelativePairs { get; set; }
        private List<Tuple<string, string>> SubjectPairs { get; set; }

        #endregion

        #region Public Methods

        /// <summary>
        /// Creates a ManyToManyRelationship for use in the BindingMap
        /// </summary>
        /// <returns>A ManyToManyRelationship for use in the BindingMap</returns>
        public ITargetRelationship GetRelationship()
        {
            var relationship = new ManyToManyRelationship()
            {
                Container = new RelationContainer
                {
                    ContainerName = this.Container,
                    RelativePairs = RelativePairs.Select(t => new RelationPair() { PairType = RelationPairType.Relative, Source = t.Value2, Target = t.Value1 }).ToList(),
                    SubjectPairs = SubjectPairs.Select(t => new RelationPair() { PairType = RelationPairType.Subject, Source = t.Value2, Target = t.Value1 }).ToList(),
                },
                Relationship = this.Relationship,
                Relative = this.Relative,
                RelativeType = this.RelativeType,
                RelativeProperty = this.RelativeProperty,
                Subject = this.Subject,
                SubjectType = this.SubjectType,
            };
            return relationship;
        }

        /// <summary>
        /// Maps a property on this class as the foreign key on the relationship table (container)
        /// </summary>
        /// <typeparam name="TSubjectProperty"></typeparam>
        /// <param name="property">The property representing the foreign key on the relationship table</param>
        /// <param name="containerColumn">The foreign key column on the relationship table</param>
        /// <returns></returns>
        public FluentManyToMany<TSubject, TRelative, TContainerSchema> SelfToContainer<TSubjectProperty, TContainerColumn>(Expression<Func<TSubject, TSubjectProperty>> property, Expression<Func<TContainerSchema, TContainerColumn>> containerColumn)
        {
            var parentExpression = property.Body as MemberExpression;
            var columnExpression = containerColumn.Body as MemberExpression;
            SubjectPairs.Add(Tuple.Create(parentExpression.GetPropertyPath().BuildString("."), columnExpression.Member.Name));
            return this;
        }

        /// <summary>
        /// Maps a property on the related class as the foreign key on the relationship table (container)
        /// </summary>
        /// <typeparam name="TRelativeProperty"></typeparam>
        /// <param name="relativeProperty">The property representing the foreign key on the relationship table</param>
        /// <param name="containerColumn">The foreign key column on the relationship table</param>
        /// <returns></returns>
        public FluentManyToMany<TSubject, TRelative, TContainerSchema> RelativeToContainer<TRelativeProperty, TContainerColumn>(Expression<Func<TRelative, TRelativeProperty>> relativeProperty, Expression<Func<TContainerSchema, TContainerColumn>> containerColumn)
        {
            var relativeExpression = relativeProperty.Body as MemberExpression;
            var columnExpression = containerColumn.Body as MemberExpression;
            RelativePairs.Add(Tuple.Create(relativeExpression.GetPropertyPath().BuildString("."), columnExpression.Member.Name));
            return this;
        }

        #endregion

        public FluentManyToMany(string property, Relationship relationship)
        {
            var schemaType = typeof (TContainerSchema);
            Container = Reflector.HasProperty(schemaType, "TableName") ? Reflector.Read(schemaType, "TableName").ToString() : schemaType.Name.Replace("Schema", "");
            RelativeProperty = property;
            Relationship = relationship;
            SubjectPairs = new List<Tuple<string, string>>();
            RelativePairs = new List<Tuple<string, string>>();
        }
    }
}